#include <stdio.h>
#include <windows.h>
#include "pThreads/semaphore.h"
#include <vector>
#include "CSemaphore.h"
#include <stdio.h>
#include <windows.h>
#include "pThreads/semaphore.h"
#include <vector>
#include "CSemaphore.h"
#include "CActionPacket.h"

#include "CConsole.h"

#define ACTIONPACKET_SIZEOFACTIONINBITS 85

CActionPacket::CActionPacket() 
{
	this->m_actorCharId = 0;
	this->m_categorie = 0;
	this->m_param1 = 0;
	this->m_startAnimCategorie = 0;
	this->m_startAnimation = 0;
	this->m_unknownValue = 0;

	this->type = 0x28;

	memset(&data[0],0,500);
}

CActionPacket::~CActionPacket() 
{
	//Free all action/target entries
	apTargetEntry* targetEntry;
	for (unsigned int targ=0;targ < this->m_targets.size(); targ++)
	{
		targetEntry = this->m_targets.at(targ);
		for (unsigned int act = 0; act < targetEntry->m_actions.size(); act++)
		{
			delete targetEntry->m_actions.at(0);
		}
		targetEntry->m_actions.clear();
		delete targetEntry;
	}
	this->m_targets.clear();
}

bool CActionPacket::loadPremade(unsigned int charId,unsigned int targId, int packetNum)
{
	bool retVal = loadPremade(packetNum,false);
	if (retVal)
	{
		this->setActorId(charId);
		for (int targetNum = 1; targetNum <= this->getNumberOfTargets(); targetNum++)
		{
			this->setTargetID(targetNum, targId);
		}
	}
	return retVal;
}

bool CActionPacket::loadPremade(int packetNum)
{
	return loadPremade(packetNum,false);
}

bool CActionPacket::loadPremade(int packetNum, bool fixCounterInSource)
{
	FILE* fIn=NULL;
	char filename[256];
	sprintf(&filename[0],"28s_%.2X.dat",packetNum);

    fIn=fopen(filename,"rb");

	if (fIn==NULL)
        return false;

	fclose(fIn);
	fIn=fopen(filename,"rb+");

	if (fIn==NULL)
        return false;

	CConsole::outDebOnly("Loading action packet from file '%s'",filename);

	if (fixCounterInSource)
	{
		//null the counter
		fseek(fIn,0x02,SEEK_SET);
		unsigned short cnt = 0x0000;
		fwrite(&cnt,2,1,fIn);
	}

	fseek(fIn,0x01,SEEK_SET); //Packet Size / 2
    fread(&(this->size),sizeof(unsigned char),1,fIn);

	fseek(fIn,4,SEEK_SET);
    fread(this->data,sizeof(char),(this->size*2)-0x04,fIn);
    fclose(fIn);

	unsigned int bitOffset=0;

	unsigned short numberOfTargets;
	unsigned char numberOfActions;

	CConsole::outDebOnly("Workload Size: 0x%.2X",CBasicPacket::unpackBitsBE(&data[0],bitOffset,8));
	bitOffset+=8;

	this->m_actorCharId = (unsigned int)CBasicPacket::unpackBitsBE(&data[0],bitOffset,32);
	CConsole::outDebOnly("Actor Char ID: 0x%.8X", this->m_actorCharId);
	bitOffset+=32;

	numberOfTargets = (unsigned short)CBasicPacket::unpackBitsBE(&data[0],bitOffset,10);
	CConsole::outDebOnly("Number of Targets(read): %d",numberOfTargets);
	bitOffset+=10;

	this->m_categorie = (unsigned char)CBasicPacket::unpackBitsBE(&data[0],bitOffset,4);
	CConsole::outDebOnly("Categorie: %d",this->m_categorie);
	bitOffset+=4;

	this->m_param1 = (unsigned short)CBasicPacket::unpackBitsBE(&data[0],bitOffset,10);
	CConsole::outDebOnly("Parameter1: 0x%.4X",this->m_param1);
	bitOffset+=10;

	this->m_startAnimCategorie = (unsigned char)CBasicPacket::unpackBitsBE(&data[0],bitOffset,6);
	CConsole::outDebOnly("Start Animation Kategorie: 0x%.4X",this->m_startAnimCategorie);
	bitOffset+=6;

	this->m_startAnimation = (unsigned short)CBasicPacket::unpackBitsBE(&data[0],bitOffset,16);
	CConsole::outDebOnly("Start Animation: 0x%.4X",this->m_startAnimation);
	bitOffset+=16;

	this->m_unknownValue = (unsigned int)CBasicPacket::unpackBitsBE(&data[0],bitOffset,32);
	CConsole::outDebOnly("Unknown: 0x%.8X",this->m_unknownValue);
	bitOffset+=32;

	apTargetEntry* targetEntry;
	apActionEntry* actionEntry;
	for (unsigned short target=0;target<numberOfTargets;target++)
	{
		targetEntry = new apTargetEntry();

		CConsole::outDebOnly("Target %d - Id: 0x%.8X",target,CBasicPacket::unpackBitsBE(&data[0],bitOffset,32));
		targetEntry->m_targetID = (unsigned int)CBasicPacket::unpackBitsBE(&data[0],bitOffset,32);
		bitOffset+=32;

		numberOfActions = (unsigned char)CBasicPacket::unpackBitsBE(&data[0],bitOffset,4);
		CConsole::outDebOnly("Target %d - Number of Actions(read): %d",target,numberOfActions);
		bitOffset+=4;

		for (unsigned char action=0;action<numberOfActions;action++)
		{
			actionEntry = new apActionEntry();

			actionEntry->unknownValue = (unsigned char)CBasicPacket::unpackBitsBE(&data[0],bitOffset,5);
			CConsole::outDebOnly("Target %d - Action %d - Unknown1: 0x%.2X",target,action,actionEntry->unknownValue);
			bitOffset+=5;

			actionEntry->targetAnimationID = (unsigned short)CBasicPacket::unpackBitsBE(&data[0],bitOffset,10);
			CConsole::outDebOnly("Target %d - Action %d - Animation: 0x%.3X",target,action,actionEntry->targetAnimationID);
			bitOffset+=10;

			actionEntry->unknownValue2 = (unsigned short)CBasicPacket::unpackBitsBE(&data[0],bitOffset,10);
			CConsole::outDebOnly("Target %d - Action %d - Unknown2: 0x%.3X",target,action,actionEntry->unknownValue2);
			bitOffset+=10;

			actionEntry->parameter2 = (unsigned short)CBasicPacket::unpackBitsBE(&data[0],bitOffset,16);
			CConsole::outDebOnly("Target %d - Action %d - Parameter2: 0x%.4X",target,action,actionEntry->parameter2);
			bitOffset+=16;

			actionEntry->messageType = (unsigned short)CBasicPacket::unpackBitsBE(&data[0],bitOffset,10);
			CConsole::outDebOnly("Target %d - Action %d - MessageType: 0x%.4X",target,action,actionEntry->messageType);
			bitOffset+=10;

			actionEntry->additionalMessages = (unsigned int)CBasicPacket::unpackBitsBE(&data[0],bitOffset,32);
			CConsole::outDebOnly("Target %d - Action %d - Additional Message Bitmask: 0x%.8X",target,action,actionEntry->additionalMessages);
			bitOffset+=32;

			actionEntry->dynamisTimerFlag = (unsigned char)CBasicPacket::unpackBitsBE(&data[0],bitOffset,2);
			CConsole::outDebOnly("Target %d - Action %d - Dynamis Time Flag (if set to 3): 0x%.2X",target,action,actionEntry->dynamisTimerFlag);
			bitOffset+=2;

			targetEntry->m_actions.push_back(actionEntry);
			actionEntry = NULL;
		}
		CConsole::outDebOnly("Target %d - Number of Actions(load): %d",target,targetEntry->m_actions.size());

		this->m_targets.push_back(targetEntry);
		targetEntry = NULL;
	}
	CConsole::outDebOnly("Number of Targets(load): %d",this->m_targets.size());
	return true;
}

unsigned char CActionPacket::calculateWorkloadSize(void)
{
	unsigned int numberOfBits=0;
	numberOfBits+=8; //Workload Byte
	numberOfBits+=32; //Actor ID
	numberOfBits+=10; //Number of Targets
	numberOfBits+=4; //Animation Categorie
	numberOfBits+=10; //Parameter 1

	numberOfBits+=22; //Begin Animation
	numberOfBits+=32; //Unknown 32 bits before first entry

	numberOfBits+=32 * this->m_targets.size();; //TargetID1
	numberOfBits+=4 * this->m_targets.size();; //Number of action entries for that target

	unsigned int numEntries=0;
	for (unsigned short targ=0;targ < this->m_targets.size();targ++)
	{
		numEntries += this->m_targets.at(targ)->m_actions.size(); 
	}

	numberOfBits+=(ACTIONPACKET_SIZEOFACTIONINBITS*numEntries);

	//Round Up;
	if ((numberOfBits%8)>0)
		numberOfBits+=8-(numberOfBits%8);

	unsigned char workloadSize = (unsigned char)(numberOfBits/8);

	//packetsize = workloadsize + sizeOfHeader + sizeof(0x0000)(endbytes)
	this->size=(workloadSize + 0x04 + 0x02); 

	//size = ceil(size/2)
	if ((size%2)>0)
		this->size++;
	this->size/=2;

	//size needs to be even, increase by 1 if it is odd
	if ((size%2)>0)
		this->size++;

	//CConsole::outDebOnly("Packet Size: %.2X",this->size);
	//CConsole::outDebOnly("Workload Size: %.2X",this->data[0]);

	//return the workload size
	return workloadSize;
}

void CActionPacket::finalizePacket(void)
{
	unsigned int bitOffset=0;

	unsigned short numberOfTargets;
	unsigned char numberOfActions;

	unsigned char workloadsize = calculateWorkloadSize();

	//Workload Size: 8 Bit
	CBasicPacket::packBitsBE(&data[0], workloadsize, bitOffset, 8);
	bitOffset+=8;

	//Actor char id: 32 Bit
	CBasicPacket::packBitsBE(&data[0], this->m_actorCharId, bitOffset, 32);
	bitOffset+=32;

	//number of targets: 10 Bit
	numberOfTargets = this->m_targets.size();
	CBasicPacket::packBitsBE(&data[0], numberOfTargets, bitOffset, 10);
	bitOffset+=10;

	//Categorie: 4 Bits
	CBasicPacket::packBitsBE(&data[0], this->m_categorie, bitOffset, 4);
	bitOffset+=4;

	//Parameter 1: 10 Bits
	CBasicPacket::packBitsBE(&data[0], this->m_param1, bitOffset, 10);
	bitOffset+=10;

	//Start animation categorie: 6 Bits
	CBasicPacket::packBitsBE(&data[0], this->m_startAnimCategorie, bitOffset, 6);
	bitOffset+=6;

	//Start animation: 16 Bits
	CBasicPacket::packBitsBE(&data[0], this->m_startAnimation, bitOffset, 16);
	bitOffset+=16;

	//Unknown Value: 32 Bits
	CBasicPacket::packBitsBE(&data[0], this->m_unknownValue, bitOffset, 32);
	bitOffset+=32;

	apTargetEntry* targetEntry;
	apActionEntry* actionEntry;
	for (unsigned short targNum = 0; targNum < numberOfTargets; targNum++)
	{
		targetEntry = this->m_targets.at(targNum);

		//Target ID: 32 Bit
		CBasicPacket::packBitsBE(&data[0], targetEntry->m_targetID, bitOffset, 32);
		bitOffset+=32;

		//Number of actions for this target: 4 Bit
		numberOfActions = targetEntry->m_actions.size();
		CBasicPacket::packBitsBE(&data[0], numberOfActions, bitOffset, 4);
		bitOffset+=4;

		for (unsigned char actionNum = 0; actionNum < numberOfActions; actionNum++)
		{
			actionEntry = targetEntry->m_actions.at(actionNum);

			//Unknown 1: 5 Bits
			CBasicPacket::packBitsBE(&data[0], actionEntry->unknownValue, bitOffset, 5);
			bitOffset+=5;

			//Animation: 10 Bits
			CBasicPacket::packBitsBE(&data[0], actionEntry->targetAnimationID, bitOffset, 10);
			bitOffset+=10;

			//Unknown 2: 10 Bits
			CBasicPacket::packBitsBE(&data[0], actionEntry->unknownValue2, bitOffset, 10);
			bitOffset+=10;

			//Param 2: 16 Bits
			CBasicPacket::packBitsBE(&data[0], actionEntry->parameter2, bitOffset, 16);
			bitOffset+=16;

			//Message type: 10 Bits
			CBasicPacket::packBitsBE(&data[0], actionEntry->messageType, bitOffset, 10);
			bitOffset+=10;

			//Additional Messages: 32 Bits
			CBasicPacket::packBitsBE(&data[0], actionEntry->additionalMessages, bitOffset, 32);
			bitOffset+=32;

			//Dynamis Timer Flag: 2 Bits
			CBasicPacket::unpackBitsBE(&data[0], actionEntry->dynamisTimerFlag, bitOffset, 2);
			bitOffset+=2;

			actionEntry = NULL;
		}

		targetEntry = NULL;
	}
}


void CActionPacket::setTargetID(unsigned char targetNum, unsigned int targetID)
{
	if (targetNum == 0)
	{
		CConsole::outErr("Trying to set the targetId for target 0!");
		return;
	}

	apTargetEntry* newTarget;
	if (targetNum <= this->m_targets.size())
	{
		newTarget = this->m_targets.at(targetNum-1);
	}
	else 
	{
		if ((this->m_targets.size()+1)!=targetNum)
		{
			CConsole::outErr("Trying to set the targetId for target %d, but there are only %d targets in the list. Will add it as target %d!",targetNum,this->m_targets.size(),this->m_targets.size()+1);
		}
		newTarget = new apTargetEntry();
		this->m_targets.push_back(newTarget);
	}

	newTarget->m_targetID = targetID;

	newTarget = NULL;
}

void CActionPacket::setActionEntry(unsigned short targetNum, unsigned char actionNum, apActionEntry* newEntry)
{
	if (targetNum == 0)
	{
		CConsole::outErr("Trying to add action for target 0!");
		return;
	}
	if (targetNum > this->m_targets.size())
	{
		CConsole::outErr("Trying to add action for target %d, but there are only %d targets in the list!", targetNum, this->m_targets.size());
		return;
	}
	if (actionNum == 0)
	{
		CConsole::outErr("Trying to add action 0 for target %d!", targetNum);
		return;
	}

	apTargetEntry* target = this->m_targets.at(targetNum-1);

	apActionEntry* newAction;

	if ( actionNum <= target->m_actions.size())
	{
		newAction = target->m_actions.at(actionNum-1);
	}
	else
	{
		if ((target->m_actions.size()+1)!=actionNum)
		{
			CConsole::outErr("Trying to add action %d for target %d, but there are only %d actions in the list. Will add it as action %d!",actionNum,targetNum,target->m_actions.size(),target->m_actions.size()+1);
		}
		newAction = new apActionEntry();
		target->m_actions.push_back(newAction);
	}

	memcpy(newAction, newEntry, sizeof(apActionEntry));
}

void CActionPacket::setActorId(unsigned int actorCharId)
{
	this->m_actorCharId = actorCharId;
}

void  CActionPacket::setCategorie(unsigned char categorie)
{
	this->m_categorie = categorie;
}

void CActionPacket::setParameter1(unsigned short param1)
{
	this->m_param1 = param1;
}

void CActionPacket::setStartAnimationCategorie(unsigned char startAnimCategorie)
{
	this->m_startAnimCategorie = startAnimCategorie;
}

void CActionPacket::setStartAnimation(unsigned short startAnimation)
{
	this->m_startAnimation = startAnimation;
}

void CActionPacket::setUnknown(unsigned int unknownValue)
{
	this->m_unknownValue = unknownValue;
}

void CActionPacket::savePacket(void)
{
	char filename[256];
	sprintf(filename,"./28s_output.dat");
	this->savePacket(filename);
}

void CActionPacket::savePacket(int num)
{
	char filename[256];
	sprintf(filename,"./28s_%.2d.dat",num);
	this->savePacket(filename);
}

void CActionPacket::savePacket(char* filename)
{
	this->finalizePacket();
	FILE* outputFile;
	outputFile=fopen(filename,"w+");
	fwrite(&type,sizeof(unsigned char),1,outputFile);
	fwrite(&size,sizeof(unsigned char),1,outputFile);
	unsigned short srvCnt=0x0000;
	fwrite(&srvCnt,sizeof(unsigned short),1,outputFile);
	fwrite(data,sizeof(unsigned char),size*2-0x04,outputFile);
	fclose(outputFile);
}

unsigned char CActionPacket::getNumberOfTargets(void)
{
	return this->m_targets.size();
}

unsigned char CActionPacket::getNumberOfActionsForTarget(unsigned char numTarget)
{
	if (numTarget>this->m_targets.size())
	{
		CConsole::outErr("Trying to get the number of actions for target %d, but there are only %d targets!", numTarget, this->m_targets.size());
		return 0;
	}
	return (this->m_targets.at(numTarget-1)->m_actions.size());
}